package com.itshidu.lock;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

@Component
public class RedisLock {
	
	private static ThreadLocal threadLocal = new ThreadLocal<Map<String, Object>>();
	
	@Autowired private StringRedisTemplate lockRedisTemplate;

	private RedisScript<Integer> lockScript=lockScript();
	private RedisScript<Integer> unLockScript=unLockScript();
    
    long acquireInterval = 100L;
    
    /**
     * <p>当锁可用，并且当前线程没有持有该锁，直接获取锁并把count set为1.</p>
     * <p>当锁可用，并且当前线程已经持有该锁，直接获取锁并把count增加1.</p>
     * <p>当锁不可用，那么当前线程被阻塞，休眠一直到该锁可以获取，然后把持有count设置为1.</p>
     * @param lockKey 
     * @param timeout
     * @param unit
     * @param maxWaitSeconds
     * @return
     */
    public String lock(String lockKey,long timeout,TimeUnit unit) {
    	
        String lockValue = UUID.randomUUID().toString();
        
        LockData pt = (LockData) threadLocal.get();
        if(pt!=null) {
        	System.out.println("可能重入");
        	if(pt.threadId==Thread.currentThread().getId()) {
    			System.out.println("Count++");
    			pt.count++;
    			return pt.code;
    		}
        	
        }else {
        	System.out.println("111111111111");
        }
        while (true) {
        	List<String> keys = Collections.singletonList(lockKey);
        	Object[] args = {lockValue,unit.toSeconds(timeout)+""};
            Number lockResult = lockRedisTemplate.execute(lockScript, keys , args);
            if (lockResult.intValue()==1) {
            	pt = new LockData();
            	pt.code=lockValue;
            	pt.threadId=Thread.currentThread().getId();
            	pt.count=1;
            	threadLocal.set(pt);
                return lockValue;
            } else {
                try {
                    Thread.sleep(acquireInterval);
                } catch (InterruptedException ex) {
                    continue;
                }
            }
        }

    }
    
    public boolean unLock(String lockKey, String lockValue) {
        Number releaseResult = lockRedisTemplate.execute(unLockScript, Collections.singletonList(lockKey), lockValue);
        LockData pt = (LockData) threadLocal.get();
        if(pt!=null) {
        	if(pt.count>1) {
        		pt.count--;
        		System.out.println("Count--");
        	}else {
        		System.out.println("Remove");
        		threadLocal.remove();
        	}
        }
        return (releaseResult.intValue()==1);
    }
    
    
    private RedisScript<Integer> lockScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("com/itshidu/lock/lock_with_timeout.lua"));
        redisScript.setResultType(Integer.class);
        return redisScript;
    }

    private RedisScript<Integer> unLockScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("com/itshidu/lock/release_lock.lua"));
        redisScript.setResultType(Integer.class);
        return redisScript;
    }
    private class LockData{
    	long threadId;
    	String code;
    	long count;
    }
}
